package naito_rescue.communication.module;

import java.util.*;
import sample.*;
import rescuecore2.messages.*;
import rescuecore2.worldmodel.*;
import rescuecore2.Constants;
import rescuecore2.config.*;
import rescuecore2.standard.components.*;
import rescuecore2.standard.entities.*;
import rescuecore2.standard.messages.*;
import rescuecore2.misc.geometry.*;

import naito_rescue.*;
import naito_rescue.agent.*;
import naito_rescue.communication.channel.*;
import naito_rescue.object.*;
import naito_rescue.message.*;
import naito_rescue.message.converter.*;
import naito_rescue.message.converter.compressor.*;
import naito_rescue.message.stream.*;

public class CommunicationModule
{
	private NAITOAgent owner;
	private MyLogger   logger;
	private StandardWorldModel model;
	
	private NAITOMessageConverterModule converter;
	private NAITOCompressorModule       compressor;
	
	private List<NAITOMessage> hasForwarded;
	public CommunicationModule(NAITOAgent owner){
		this.owner = owner;
		this.model = this.owner.getWorldModel();
		this.logger = owner.getLogger();
		this.converter = new NAITOMessageConverterModule();
		this.compressor = new NAITOCompressorModule();
		this.hasForwarded = new ArrayList<NAITOMessage>();
	}
	
	public void sendMessages(List<NAITOMessage> messages,
	                         AbstractChannel    channel,
	                         int                maxBandwidth,
	                         VoiceChannel       voiceChannel){
		int bandwidthLeft = maxBandwidth;
		String context = "CommunicationModule#sendMessages";
		
		if(messages.isEmpty()){
			return;
		}
		/*
		logger.info("To Channel " + channel.getChannelNum() + ", " + 
					"We will send " + messages.size() + " messages.", "CommunicationModule#sendMessages");
		*/
		//int compressSize = (compressor.compress(converter.encodeMessages(messages))).length;
		//logger.debug(messages.size() + " messages are = " + messages + " , compressed->" + compressSize,"CommModule#sendMessages");
		if(bandwidthLeft < 0){
			//logger.info("bandwidthLeft < 0, cannot send messages.", "CommunicationModule#sendMessages");
			return;
		}
		int count = 1;		
		int currentSize = messages.size();
		while(!messages.isEmpty() && bandwidthLeft > 0){
			//logger.trace("messages are = " + messages + " (" + count + " loop)", "CommModule#sendMessages");
			//logger.trace("bandwidthLeft = " + bandwidthLeft + " (" + count + " loop)", "CommModule#sendMessages");
			
			currentSize = messages.size();
			int currentBW = bandwidthLeft;
			byte[] compressData = compressMessages(messages, bandwidthLeft);
			if(compressData == null){
				//帯域不足によりメッセージの送信が不可能
				//logger.info("Compressed data is null! ");
				return;
			}else{
				//logger.info("Speak to " + channel.getChannelNum() + "th channel. used " + compressData.length + " bytes.", context);
				owner.speak(channel.getChannelNum(), compressData);
				//forward to voicechannel.
				if(channel.getChannelNum() != voiceChannel.getChannelNum()){
					// //logger.info("Forward messages to voice-channel. voice-channel = " + voiceChannel.getChannelNum() + " .", context);
					owner.speak(voiceChannel.getChannelNum(), compressData);
					//logger.info("Forward some messages, Used " + compressData.length + " bytes of Voice Channel. ", "CommunicationModule#sendMessages");
				}
				voiceChannel.setUsedBandWidth(compressData.length, owner.getTime());
				hasForwarded.addAll(messages);
			}
			
			Iterator<NAITOMessage> iterator = messages.iterator();
			while(iterator.hasNext()){
				NAITOMessage message = iterator.next();
				if(message.hasSent()){
					//logger.debug("Message has sent remove. ", "CommunicationModule#sendMessages");
					iterator.remove();
				}
			}
			
			bandwidthLeft -= compressData.length;
			
			//logger.debug("After speaking, messages size = " + messages.size() + " (" + (currentSize-messages.size()) + " messages sent), "
			//	+ "bandwidthLeft = " + bandwidthLeft + " (" + (currentBW-bandwidthLeft) + " bytes used)");
			//logger.trace("messages are = " + messages + " (" + count + " loop, after speaking)", "CommModule#sendMessages");
			//logger.trace("bandwidthLeft = " + bandwidthLeft + " (" + count + " loop, after speaking)", "CommModule#sendMessages");
		}
		
		//logger.debug("We sent " + (maxBandwidth - bandwidthLeft) + " bytes. We sent" + (currentSize - messages.size()) + " messages.",
		//			"CommunicationModule#sendMessages");
		
	}
	private byte[] compressMessages(List<NAITOMessage> messages,
	                                int maxBandwidth){
	    
	    int   lastIndexOfSendableMessage = -1;
	    // メッセージ列の末尾に付加する1バイトのNULLを除いた通信容量
	    int   maxBandwidthWithoutTail = maxBandwidth - 1;
	    //dataLength:
	    //dataLengths[0] = compress(messages.get(0)).length;
	    //dataLengths[1] = compress(messages.get(0), messages.get(1)).length;
	    //dataLengths[n] = compress(messages.get(0),...,messages.get(n)).length;
	    int[] dataLengths = new int[messages.size()];
	    byte[] result = null;
	    List<NAITOMessage> sent = new ArrayList<NAITOMessage>();
	    
	    for(int i = 0;i < messages.size();i++){
	    	NAITOMessage message = messages.get(i);
	    	sent.add(message);
	    	RawDataOutputStream stream = converter.encodeMessages(sent);
	    	dataLengths[i] = compressor.compress(stream).length;
	    	//logger.debug("dataLengths[" + i + "]=" + dataLengths[i], "compressMessages");
	    }
	    
	    for(int i = dataLengths.length-1;i >= 0;i--){
	    	if(dataLengths[i] < maxBandwidthWithoutTail){
	    		lastIndexOfSendableMessage = i;
	    		break;
	    	}
	    }
	    if(lastIndexOfSendableMessage != -1){
	    	//logger.info("We will send " + (lastIndexOfSendableMessage+1) + " messages (all " + messages.size() + " messages)", "compressMessages");
	    	List<NAITOMessage> subList = messages.subList(0, lastIndexOfSendableMessage+1);
	    	for(NAITOMessage message : subList){
	    		message.markAsSent();
	    	}
	    	RawDataOutputStream stream = converter.encodeMessages(subList);
	    	try{
	    		//ストリームの末尾にNULLを書き込む(メッセージ列の終端を表すため)
	    		stream.writeByte(MessageType.NULL.type());
	    	}catch(Exception e){
	    		e.printStackTrace();
	    	}
	    	result = compressor.compress(stream);
	    }else{
	    	//logger.debug("lastIndexOfSendableMessage == -1 ... ", "compressMessage-DEBUG");
	    	//logger.debug("maxBandwidthWithoutTail = " + maxBandwidthWithoutTail, "compressMessage-DEBUG");
	    	for(int i = 0;i < dataLengths.length;i++){
	    		//logger.debug("dataLengths[" + i + "]=" + dataLengths[i], "compressMessage-DEBUG");
	    	}
	    }
		return result;
	}
	
	public List<NAITOMessage> receiveMessages(AKSpeak speak, VoiceChannel voiceChannel){
		byte[] compressedRawData = speak.getContent();
		EntityID senderID = speak.getAgentID();
		if(compressedRawData == null || compressedRawData.length == 0){
			//logger.info("zero message has received. ==> return empty list.");
			return Collections.emptyList();			
		}
		
		RawDataInputStream stream = compressor.decompress(compressedRawData);
		if(stream == null){
			return Collections.emptyList();
		}
		List<NAITOMessage> result = converter.decodeMessages(stream);
		List<NAITOMessage> willForward = new ArrayList<NAITOMessage>();
		for(NAITOMessage received : result){
			if(!(hasForwarded.contains(received))){
				willForward.add(received);
			}
		}
		if(!willForward.isEmpty() && shouldForwardMessages(senderID)){
			//forward.
			//logger.info("Forward some messages. Remainder bandwidth of Voice Channel is = " + voiceChannel.getRemaindBandwidth(owner.getTime()) + ". Num of forward messeages are = " + willForward.size(), "CommunicationModule#receiveMessages");
			sendMessages(willForward, voiceChannel, voiceChannel.getRemaindBandwidth(owner.getTime()), voiceChannel);
		}
		//logger.info("We received " + result.size() + " messages.", "CommunicationModule#receiveMessages");
		return result;
	}
	private boolean shouldForwardMessages(EntityID senderID){
		Set<Human> humans = owner.getHumansInView();
		int sender = senderID.getValue();
		for(Human human : humans){
			EntityID id = human.getID();
			int id_value = id.getValue();
			if(id_value == owner.getID().getValue() || id_value == sender){
				continue;
			}else{
				// 自分とsender以外のHumanがいたらforwardする
				//logger.debug("Messages should forwarded. human = " + human + ", sender = " + senderID, "CommunicationModule#shouldForwardMessages");
				return true;
			}
		}
		return false;
	}
}
